package com.acme.bankapp.domain;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import com.acme.bankapp.domain.Bank;
import com.acme.bankapp.domain.Bank.ClientRegistrationListener;
import com.acme.bankapp.domain.Client;
import com.acme.bankapp.domain.accounting.Account;
import com.acme.bankapp.domain.accounting.AcountHasOwnerException;
import com.acme.bankapp.domain.accounting.CheckingAccount;
import com.acme.bankapp.domain.accounting.NotEnoughFundsException;
import com.acme.bankapp.domain.accounting.SavingAccount;
import com.acme.bankapp.services.DataWorker;

public class BankService {

	public static final String ACCOUNTS_INFORMATION_REPORT_HEADER = "Accounts";
	public static final String CLIENTS_LIST_REPORT_HEADER = "Bank clients";
	public static final String CLIENTS_ACCOUNTS_REPORT_HEADER = "Clients accounts";

	private DataWorker dataWorker;

	public BankService() {
		createBank(new ArrayList<Bank.ClientRegistrationListener>());
	}

	/**
	 * Controlled bank
	 */
	private Bank bank;

	public Bank getBank() {
		return bank;
	}

	/**
	 * Sets DataWorker for BankService. BankService cab backup bank to
	 * DataWorker every time smth changed
	 * 
	 * @param pathname
	 *            file path for DataFile
	 */
	public void setDataWorker(DataWorker worker) {
		this.dataWorker = worker;
	}

	/**
	 * Add client to bank
	 * 
	 * @param bank
	 * @param client
	 */
	public void addClient(Client client) throws ClientExistsException {
		bank.addClient(client);

		saveBank();
	}

	/**
	 * Add account to client's accounts.
	 * 
	 * @param client
	 *            Client
	 * @param account
	 *            Account
	 * @throws AccountExistsException
	 *             client already has this account
	 * @throws AcountHasOwnerException
	 */
	public void addAccount(Client client, Account account)
			throws AccountExistsException, AcountHasOwnerException {
		if (account.getClient() != null)
			throw new AcountHasOwnerException(account.toString(), account
					.getClient().getClientSalutation());
		account.setClient(client);
		if (client.getAccounts().contains(account))
			throw new AccountExistsException(account.toString(),
					client.getClientSalutation());
		client.addAccount(account);

		saveBank();
	}

	/**
	 * Deposit to account
	 * 
	 * @param account
	 *            Account
	 * @param x
	 *            Value to deposit
	 */
	public void deposit(Account account, double x) {
		account.deposit(x);

		saveBank();
	}

	/**
	 * Withdraw from account
	 * 
	 * @param account
	 *            Account
	 * @param x
	 *            Value to withdraw
	 * @throws LowBalanceException
	 *             Error withdrawing if X is greater than available amount to
	 *             withdraw
	 */
	public void withdraw(Account account, double x)
			throws NotEnoughFundsException {
		account.withdraw(x);

		saveBank();
	}

	/**
	 * new bank
	 * 
	 * @return clean bank
	 */
	public void createBank(List<ClientRegistrationListener> listeners) {

		Bank bank = new Bank(listeners, new ArrayList<Client>());

		this.bank = bank;
	}

	/**
	 * Add client registration listener to bank
	 * 
	 * @param listener
	 */
	public void addClientRegistrationListener(
			ClientRegistrationListener listener) {
		this.bank.addClientRegistrationListener(listener);
	}

	/**
	 * Save serialized bank to file
	 * 
	 * @param bank
	 *            Bank object
	 * @param filename
	 *            filename
	 */
	public void saveBank() {
		if (dataWorker == null)
			return;
		try {
			dataWorker.save(bank);
		} catch (IOException e) {
			System.err.println(e.getMessage());
			e.printStackTrace();
		}
	}

	/**
	 * Restore bank from serialized object
	 * 
	 * @return Bank
	 * @throws ClassNotFoundException
	 * @throws IOException
	 */
	public void loadBank() throws IOException {
		if (dataWorker == null)
			return;
		try {
			List<ClientRegistrationListener> listeners = bank
					.getClientRegistrationListeners();
			this.bank = (Bank) dataWorker.restore();
			this.bank.setClientRegistrationListeners(listeners);
		} catch (ClassNotFoundException e) {
			System.out.println("Bad bank file");
		}
	}

	/**
	 * Fill banks clients from external DataWorker file
	 * 
	 * @param feedWorker
	 *            external DataWorker file, which incapsulate path to file with
	 *            feed info
	 */
	public void fillClients(DataWorker feedWorker) {

		String feedString = feedWorker.loadFeed();

		try {
			for (String clientString : feedString.split("\n")) {
				String[] clientInfo = clientString.split(":", 3);
				String name = clientInfo[0];
				Gender gender = (clientInfo[1].equals("m")) ? Gender.MALE
						: Gender.FEMALE;
				Client client = createClient(name, gender);
				for (String accountString : clientInfo[2].split(";")) {
					String[] accountInfo = accountString.split("/");
					Account account = null;
					if ("s".equals(accountInfo[0])) {
						account = new SavingAccount(
								Double.parseDouble(accountInfo[1]));
					} else if ("c".equals(accountInfo[0])) {
						account = new CheckingAccount(
								Double.parseDouble(accountInfo[1]),
								Double.parseDouble(accountInfo[2]));
					}
					if (account != null)
						addAccount(client, account);
				}
				this.bank.addClient(client);
			}
		} catch (ClientExistsException | AccountExistsException
				| AcountHasOwnerException e) {
			System.err.println("Error filling clients!");
		}

	}

	/**
	 * Creates client
	 * 
	 * @param name
	 *            Client name
	 * @param gender
	 *            Client gender
	 * @return Client
	 */
	public Client createClient(String name, Gender gender) {
		Client client = new Client(name, gender);
		client.setAccounts(new ArrayList<Account>());
		return client;
	}

	/**
	 * Bank's clients list report
	 * 
	 * @param bank
	 *            Bank
	 * @return Report
	 */
	public String reportBankClients() {
		StringBuilder builder = new StringBuilder();
		builder.append("\n").append(CLIENTS_LIST_REPORT_HEADER).append("\n");
		for (Client client : bank.getClients()) {
			builder.append(client.getClientSalutation()).append("\n");
		}
		return builder.toString();
	}

	/**
	 * Amounts to withdraw report over all clients
	 * 
	 * @param bank
	 * @return report
	 */
	public String reportAccountsInformation() {
		StringBuilder builder = new StringBuilder();
		builder.append("\n").append(ACCOUNTS_INFORMATION_REPORT_HEADER)
				.append("\n");
		for (Client client : bank.getClients()) {
			builder.append(getAccountsInformation(client));
		}

		return builder.toString();
	}

	/**
	 * Client's account report
	 * 
	 * @param client
	 *            Client
	 * @return report
	 */
	public String reportClientsAccounts(Client client) {
		StringBuilder builder = new StringBuilder();
		builder.append("\n").append(CLIENTS_ACCOUNTS_REPORT_HEADER)
				.append("\n");
		builder.append(getAccountsInformation(client));

		return builder.toString();
	}

	private String getAccountsInformation(Client client) {
		return client.toString();
	}
}
